package cc.rengu.redp.flowable.service.impl;

import cc.rengu.redp.common.exception.RedpException;
import cc.rengu.redp.common.utils.DateUtil;
import cc.rengu.redp.flowable.domain.*;
import cc.rengu.redp.flowable.service.SysWorkFlowModelService;
import cc.rengu.redp.flowable.service.SysWorkFlowService;
import cc.rengu.redp.flowable.service.WorkFlowVar;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.*;
import org.flowable.engine.history.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.engine.task.Comment;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.idm.api.Group;
import org.flowable.idm.api.User;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.stream.Collectors;


/**
 * 工作流引擎服务层
 *
 * @author xushitang
 */
@Slf4j
@Service
public class SysWorkFlowServiceImpl implements SysWorkFlowService {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private IdentityService identityService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private SysWorkFlowModelService sysWorkFlowModelService;

    private final static String PROC_STATUS_FINISHED = "1";
    private final static String PROC_STATUS_UNFINISHED = "2";
    private final static String PROC_STATUS_DELETED = "3";
    private final static String PROC_STATUS_NOT_DELETED = "4";

    @Override
    public void saveUser(String userId) {
        User idmUser = identityService.newUser(userId);
        identityService.saveUser(idmUser);
    }

    @Override
    public void deleteUser(String userId) {
        List<Group> idmGroupList = identityService.createGroupQuery().groupMember(userId).list();
        idmGroupList.forEach(idmGroup -> identityService.deleteMembership(userId, idmGroup.getId()));
        identityService.deleteUser(userId);
    }

    @Override
    public void saveGroup(String groupName) {
        Group idmGroup = identityService.newGroup(groupName);
        identityService.saveGroup(idmGroup);
    }

    @Override
    public void deleteGroup(String groupName) {
        List<User> idmUserList = identityService.createUserQuery().memberOfGroup(groupName).list();
        idmUserList.forEach(idmUser -> identityService.deleteMembership(idmUser.getId(), groupName));
        identityService.deleteGroup(groupName);
    }

    @Override
    public void createMembership(String userId, List<String> groupNameList) {
        List<Group> idmGroupList = identityService.createGroupQuery().groupMember(userId).list();
        idmGroupList.forEach(idmGroup -> identityService.deleteMembership(userId, idmGroup.getId()));
        groupNameList.forEach(groupName -> identityService.createMembership(userId, groupName));
    }

    @Override
    public List<User> getAllByRoles(List<String> groups) {
        return identityService.createUserQuery().memberOfGroups(groups).list();
    }

    @Override
    public List<ProcessDefinition> getAvailableProcesses(String userId) {
        return repositoryService.createProcessDefinitionQuery().startableByUser(userId).active().latestVersion().list();
    }

    @Override
    public List<ProcessDefinition> getAvailableProcesses() {
        return repositoryService.createProcessDefinitionQuery().active().latestVersion().list();
    }

    @Override
    public List<ProcessDefinition> getAllProcesses() {
        return repositoryService.createProcessDefinitionQuery().latestVersion().list();
    }

    @Override
    public ProcessDefinition getProcessDefinitionByProcDefKey(String procDefKey) {
        return repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey).latestVersion().singleResult();
    }

    @Override
    public BpmnModel getBpmnModelFromRepositoryByProcDefKey(String procDefKey) {
        ProcessDefinition pd = repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionKey(procDefKey)
                .latestVersion()
                .singleResult();
        return repositoryService.getBpmnModel(pd.getId());
    }

    @Override
    public Map<String, String> getEndEventsByProcDefId(String procDefKey) {
        ProcessDefinition pd = repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionKey(procDefKey)
                .latestVersion()
                .singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(pd.getId());
        Process process = bpmnModel.getProcesses().get(0);
        Collection<FlowElement> flowElements = process.getFlowElements();
        Map<String, String> endEvents = new HashMap<>();

        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof EndEvent) {
                EndEvent endEvent = (EndEvent) flowElement;
                endEvents.put(endEvent.getId(), endEvent.getName());
            }
        }

        return endEvents;
    }

    @Override
    public void completeTaskByTaskId(String taskId, WorkFlowVar workFlowVar) {
        taskService.complete(taskId, workFlowVar.getVarMap());
    }

    @Override
    public void completeTaskByTaskId(String taskId, WorkFlowVar workFlowVar, String assignee) {
        taskService.claim(taskId, assignee);
        taskService.complete(taskId, workFlowVar.getVarMap());
    }

    @Override
    public void completeTaskByProcInstId(String procInstId, WorkFlowVar workFlowVar) {
        List<String> taskIdList = this.getCurrTaskByProcInstId(procInstId);
        taskIdList.forEach(taskId -> completeTaskByTaskId(taskId, workFlowVar));
    }

    @Override
    public void completeTaskByProcInstId(String procInstId, WorkFlowVar workFlowVar, String assignee) {
        List<String> taskIdList = this.getCurrTaskByProcInstId(procInstId);
        taskIdList.forEach(taskId -> completeTaskByTaskId(taskId, workFlowVar, assignee));
    }

    @Override
    public void completeReceiveTask(String procInstId, String receiveTaskDefId) {
        Execution execution = runtimeService.createExecutionQuery()
                .processInstanceId(procInstId).activityId(receiveTaskDefId).singleResult();
        runtimeService.trigger(execution.getId());
    }

    @Override
    public List<String> getTaskIdByProcInstId(String procInstId) {
        List<Task> taskList = taskService.createTaskQuery()
                .processInstanceId(procInstId)
                .list();
        List<String> taskIdList = new ArrayList<>();
        taskList.forEach(task -> taskIdList.add(task.getId()));
        return taskIdList;
    }

    @Override
    public List<String> getNodeIdByProcInstId(String procInstId) {
        //查询流程实例
        List<ProcessInstance> piList = runtimeService.createProcessInstanceQuery()//创建流程实例查询
                .processInstanceId(procInstId)//使用流程实例ID查询
                .list();
        List<String> nodeIdList = new ArrayList<>();
        piList.forEach(pi -> nodeIdList.add(pi.getId()));
        return nodeIdList;
    }

    @Override
    public List<WaitingToDo> getWaitingToDo(String userId, String queryText, int pageNo, int pageSize) {
        List<WaitingToDo> waitingToDos = new ArrayList<>();
        TaskQuery taskQuery;
        if (StringUtils.isBlank(queryText)) {
            taskQuery = taskService.createTaskQuery()
                    .taskCandidateOrAssigned(userId)
                    .orderByTaskCreateTime()
                    .desc();
        } else {
            taskQuery = taskService.createTaskQuery()
                    .taskCandidateOrAssigned(userId)
                    .or()
                    .processInstanceId(queryText)
                    .processDefinitionNameLike("%" + queryText + "%")
                    .endOr()
                    .orderByTaskCreateTime()
                    .desc();
        }

        taskQuery.listPage((pageNo - 1) * pageSize, pageSize).forEach(task -> {
            ProcessInstance procInst = runtimeService
                    .createProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .singleResult();
            WaitingToDo waitingToDo = new WaitingToDo();
            waitingToDo.setTaskId(task.getId());
            waitingToDo.setProcInstId(task.getProcessInstanceId());
            waitingToDo.setProcDefName(procInst.getProcessDefinitionName());
            waitingToDo.setApplicant(procInst.getStartUserId());
            waitingToDo.setArrivalDate(task.getCreateTime());
            CustomNodeVo customNodeVo = JSON.parseObject(task.getDescription(), CustomNodeVo.class);
            customNodeVo.setNodeName(task.getName());
            waitingToDo.setCurrNode(customNodeVo);
            waitingToDos.add(waitingToDo);
        });

        return waitingToDos;
    }

    @Override
    public long getWaitingToDoTotal(String userId, String queryText) {
        TaskQuery taskQuery;
        if (StringUtils.isBlank(queryText)) {
            taskQuery = taskService.createTaskQuery()
                    .taskCandidateOrAssigned(userId);
        } else {
            taskQuery = taskService.createTaskQuery()
                    .taskCandidateOrAssigned(userId)
                    .or()
                    .processInstanceId(queryText)
                    .processDefinitionNameLike("%" + queryText + "%")
                    .endOr();
        }

        return taskQuery.count();
    }

    @Override
    public String getNodeIdByTaskId(String taskId) {
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult();
        if (task != null) {
            return task.getTaskDefinitionKey();
        } else {
            return null;
        }
    }

    @Override
    public String getProcInstByTaskId(String taskId) {
        return taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
    }

    @Override
    public String getProcDefNameByProcInstId(String procInstId) {
        String procDefId = getProcDefIdByProcInstId(procInstId);
        return repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionKey(procDefId)
                .latestVersion()
                .singleResult()
                .getName()
                .trim();
    }

    @Override
    public String getProcDefIdByProcInstId(String procInstId) {
        return runtimeService.createProcessInstanceQuery()
                .processInstanceId(procInstId)
                .singleResult()
                .getProcessDefinitionKey();
    }

    @Override
    public String getProcDefIdByTaskId(String taskId) {
        String procDefId = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult()
                .getProcessDefinitionId();
        return procDefId.substring(0, procDefId.indexOf(":"));
    }

    @Override
    public String getProcDefIdVerIdByInstId(String procInstId) {
        return runtimeService.createProcessInstanceQuery()
                .processInstanceId(procInstId)
                .singleResult()
                .getProcessDefinitionId();
    }

    @Override
    public String getProcDefVerIdByTaskId(String taskId) {
        String procDefId = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult()
                .getProcessDefinitionId();
        return procDefId;
    }


    @Override
    public List<Comment> getCommentByProcInstId(String processInstId) {
        return taskService.getProcessInstanceComments(processInstId);
    }

    @Override
    public String getCommentByTaskId(String taskId) {
        List<Comment> listComment = taskService.getTaskComments(taskId, "comment");
        if (listComment.size() > 0) {
            return listComment.get(0).getFullMessage();
        } else {
            return null;
        }
    }

    @Override
    public List<String> getCurrTaskByProcInstId(String procInstId) {
        List<Task> taskList = taskService
                .createTaskQuery()
                .processInstanceId(procInstId)
                .orderByTaskCreateTime()
                .desc()
                .list();
        List<String> taskIdList = new ArrayList<>();
        taskList.forEach(task -> taskIdList.add(task.getId()));
        return taskIdList;
    }

    @Override
    public <T extends WorkFlowVar> T getVariable(String taskId, Class<T> clazz) {
        T workFlowVarInstance = null;
        try {
            workFlowVarInstance = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
        Map<String, Object> varMap = taskService.getVariables(taskId);
        workFlowVarInstance.setVarMap(varMap);
        return workFlowVarInstance;
    }

    @Override
    public void setVariable(String taskId, WorkFlowVar workFlowVar) {
        taskService.setVariables(taskId, workFlowVar.getVarMap());
    }

    @Override
    public void setAssignee(String taskId, String assignee) {
        taskService.setAssignee(taskId, assignee);
    }

    @Override
    public void setCandidate(String taskId, List<String> candidates) {
        candidates.forEach(assignee -> taskService.addCandidateUser(taskId, assignee));
    }

    @Override
    public String getApplicantByProcInstId(String procInstId) {
        return runtimeService.createProcessInstanceQuery().processInstanceId(procInstId).singleResult().getStartUserId();
    }

    @Override
    public String getHistAssigneeInThisProcInstByTaskId(String taskId) {
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult();

        return getHistAssigneeInThisProcInstByNodeId(task.getProcessInstanceId(), task.getTaskDefinitionKey());
    }

    @Override
    public String getHistAssigneeInThisProcInstByNodeId(String procInstId, String nodeId) {
        List<HistoricTaskInstance> historicTaskInstances = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(procInstId)
                .taskDefinitionKey(nodeId)
                .orderByHistoricTaskInstanceStartTime()
                .desc()
                .list();

        if (historicTaskInstances.size() != 0) {
            return historicTaskInstances.get(0).getAssignee();
        }

        return null;
    }

    @Override
    public void setComment(String processInstanceId, String taskId, String userName, String message) {
        //增加批注信息
        Authentication.setAuthenticatedUserId(userName);
        taskService.addComment(taskId, processInstanceId, message);
    }

    @Override
    public void delComment(String taskId) {
        taskService.deleteComments(taskId, null);
    }

    @Override
    public String startProcess(String processDefinitionKey, String applicant, String businessKey, WorkFlowVar workFlowVar) {
        identityService.setAuthenticatedUserId(applicant.trim());

        ProcessInstance pi;
        if (workFlowVar == null) {
            pi = runtimeService.startProcessInstanceByKey(
                    processDefinitionKey,
                    businessKey,
                    null);
        } else {
            pi = runtimeService.startProcessInstanceByKey(
                    processDefinitionKey,
                    businessKey,
                    workFlowVar.getVarMap());
        }

        if (pi != null) {
            return pi.getProcessInstanceId();
        } else {
            return null;
        }
    }

    @Override
    public ProcessDefinition getProcDefByProcDefKey(String procDefKey) {
        return repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(procDefKey)
                .latestVersion()
                .singleResult();
    }

    @Override
    public Deployment getDeploymentById(String deployId) {
        return repositoryService.createDeploymentQuery()
                .deploymentId(deployId)
                .singleResult();
    }

    @Override
    public List<HisActivity> getHisActByProcInstId(String procInstId) {
        List<HisActivity> listHisActivity = new ArrayList<HisActivity>();
        //流程历史查询
        Set<String> activityTypes = new HashSet<>();
        activityTypes.add("userTask");
        activityTypes.add("serviceTask");
        activityTypes.add("receiveTask");
        activityTypes.add("callActivity");
        activityTypes.add("startEvent");
        activityTypes.add("endEvent");
        List<HistoricActivityInstance> listHistoricActivityInstance = historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(procInstId)
                .activityTypes(activityTypes)
                .list();
        Collections.sort(listHistoricActivityInstance, (a, b) -> {
            int compare = a.getStartTime().compareTo(b.getStartTime());
            if (compare != 0) {
                return 0 - compare;
            } else {
                if (a.getEndTime() == null) return -1;
                if (b.getEndTime() == null) return 1;
                return 0 - a.getEndTime().compareTo(b.getEndTime());
            }
        });
        //装载展示数据
        for (HistoricActivityInstance hisActIns : listHistoricActivityInstance) {
            HisActivity hisActivity = new HisActivity();
            hisActivity.setTaskId(hisActIns.getTaskId());
            if ("userTask".equals(hisActIns.getActivityType().trim())) {
                hisActivity.setActivityType("页面");
                hisActivity.setActivityName(hisActIns.getActivityName());
                hisActivity.setAssignee(hisActIns.getAssignee());
            } else if ("serviceTask".equals(hisActIns.getActivityType().trim()) ||
                    "receiveTask".equals(hisActIns.getActivityType().trim()) ||
                    "callActivity".equals(hisActIns.getActivityType().trim())) {
                hisActivity.setActivityType("后台");
                hisActivity.setActivityName(hisActIns.getActivityName());
                hisActivity.setAssignee("[SYSTEM]");
            } else if ("startEvent".equals(hisActIns.getActivityType().trim())) {
                hisActivity.setActivityType("开始");
                hisActivity.setActivityName(hisActIns.getActivityName());
                String applicant = historyService
                        .createHistoricProcessInstanceQuery()
                        .processInstanceId(procInstId)
                        .singleResult()
                        .getStartUserId();
                hisActivity.setAssignee(applicant);
            } else if ("endEvent".equals(hisActIns.getActivityType().trim())) {
                hisActivity.setActivityType("结束");
                hisActivity.setActivityName(hisActIns.getActivityName());
            } else {
                continue;
            }

            hisActivity.setStartTime(DateUtil.getDateFormat(hisActIns.getStartTime(), DateUtil.TIME_PATTERN));
            hisActivity.setStartDate(DateUtil.getDateFormat(hisActIns.getStartTime(), DateUtil.DATE_PATTERN));
            if (hisActIns.getEndTime() != null) {
                hisActivity.setEndTime(DateUtil.getDateFormat(hisActIns.getEndTime(), DateUtil.TIME_PATTERN));
                hisActivity.setEndDate(DateUtil.getDateFormat(hisActIns.getEndTime(), DateUtil.DATE_PATTERN));
            }
            if (hisActIns.getDurationInMillis() != null) {
                hisActivity.setDuration(DateUtil.getHumanDisplayForTimediff(hisActIns.getDurationInMillis()));
            }
            List<Comment> listComments = taskService.getTaskComments(hisActIns.getTaskId(), "comment");
            if (listComments.size() > 0) {
                hisActivity.setComment(listComments.get(0).getFullMessage());
            }

            listHisActivity.add(hisActivity);
        }

        return listHisActivity;
    }

    @Override
    public Long getHisActByProcInstIdTotal(String procInstId) {
        return historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(procInstId)
                .count();
    }


    @Override
    public void terminateProcInst(String procInstId) {
        runtimeService.deleteProcessInstance(procInstId, "强制关闭");
    }

    @Override
    public void terminateProcessFromHis(String procInstId) {
        historyService.deleteHistoricProcessInstance(procInstId);
    }

    public List<NextToDo> getOutComeListByTaskId(String taskId, BiFunction<String, String, Boolean> getDataAndCheck) {
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult();
        String processDefinitionId = task.getProcessDefinitionId();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        //找出当前节点的入线
        ActivityInstance activityInstance = runtimeService.createActivityInstanceQuery()
                .executionId(task.getExecutionId())
                .activityType("sequenceFlow")
                .orderByActivityInstanceStartTime().desc().list().get(0);
        FlowElement incomingFlow = bpmnModel.getFlowElement(activityInstance.getActivityId());
        //获取入线参数
        CustomSequenceFlow incomingCustom = JSON.parseObject(incomingFlow.getDocumentation(), CustomSequenceFlow.class);
        //获取出线限定列表
        List<String> decideOutgoing = new ArrayList<>();
        if (incomingCustom != null && incomingCustom.getIncomingDecideOutgoings().size() > 0) {
            decideOutgoing.addAll(
                    incomingCustom.getIncomingDecideOutgoings()
                    .stream().map(i -> i.getSequenceFlowId()).collect(Collectors.toList()));
        }

        //获取节点的出线
        UserTask userTask = (UserTask)bpmnModel.getFlowElement(task.getTaskDefinitionKey());
        List<SequenceFlow> outgoingFlows = userTask.getOutgoingFlows();
        if (outgoingFlows == null) {
            log.error("getOutComeListByTaskId error, outgoingFlows not found.");
            return null;
        }

        //如果设置了Aviator，执行判断过滤一次
        outgoingFlows = outgoingFlows.stream().filter(ogf -> {
            CustomSequenceFlow customSequenceFlow = JSON.parseObject(ogf.getDocumentation(), CustomSequenceFlow.class);
            if (customSequenceFlow == null || customSequenceFlow.getDisplayRule() == null) return true;  // 没有设置，不过滤
            String aviatorExpression = customSequenceFlow.getDisplayRule().getAviatorExpression();
            if (StringUtils.isBlank(aviatorExpression)) {
                return true;  // 没有设置，不过滤
            } else {
                //回调方法判断Aviator表达式，参数：流程实例Id，aviator表达
                return getDataAndCheck.apply(task.getProcessInstanceId(), aviatorExpression);
            }
        }).collect(Collectors.toList());

        //出线转换成nextToDos，并且按出线限定过滤
        List<NextToDo> nextToDos = new ArrayList<>();
        for (SequenceFlow sequenceFlow : outgoingFlows) {
            if (decideOutgoing.size() > 0) {
                // 设置了出线限定时，只输出指定分支
                if (decideOutgoing.contains(sequenceFlow.getId())) {
                    nextToDos.add(new NextToDo(sequenceFlow.getId(), sequenceFlow.getName()));
                } else {
                    continue;
                }
            } else {
                nextToDos.add(new NextToDo(sequenceFlow.getId(), sequenceFlow.getName()));
            }
        }

        return nextToDos;
    }

    @Override
    public CustomSequenceFlow getCustomSequenceFlowByTaskIdAndSeqId(String taskId, String seqId) {
        //1:使用任务ID，查询任务对象
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult();
        //2：获取流程定义ID
        String processDefinitionId = task.getProcessDefinitionId();
        //3：获取BPMN模型
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        Process process = bpmnModel.getProcesses().get(0);
        FlowElement flowElement = process.getFlowElement(seqId);
        if (flowElement == null) {
            return null;
        }

        return JSON.parseObject(flowElement.getDocumentation(), CustomSequenceFlow.class);
    }

    @Override
    public void deployFromModeler(String modelId) throws RedpException {
        BpmnModel bpmnModel = sysWorkFlowModelService.beforeDeploy(modelId);
        //发布流程
        repositoryService.createDeployment().addBpmnModel(bpmnModel.getProcesses().get(0).getName() + ".bpmn", bpmnModel).deploy();
    }

    @Override
    public CustomUserNode getCustomNodeByTaskId(String taskId) {
        String jsonStr = taskService.createTaskQuery().taskId(taskId).singleResult().getDescription();
        CustomUserNode customUserNode = JSONObject.parseObject(jsonStr, CustomUserNode.class);
        return customUserNode;
    }

    @Override
    public CustomStartNode getStartCustomNodeByProcDefId(String procDefId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(procDefId);
        Process process = bpmnModel.getProcesses().get(0);
        Collection<FlowElement> flowElements = process.getFlowElements();

        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof StartEvent) {
                StartEvent startEvent = (StartEvent) flowElement;
                String jsonStr = startEvent.getDocumentation();
                CustomStartNode customStartNode = JSONObject.parseObject(jsonStr, CustomStartNode.class);
                return customStartNode;
            }
        }

        return null;
    }

    @Override
    public CustomStartNode getStartCustomNodeByProcDefKey(String procDefKey) {
        ProcessDefinition processDefinition = getProcDefByProcDefKey(procDefKey);
        if (processDefinition == null) {
            return null;
        }

        return getStartCustomNodeByProcDefId(processDefinition.getId());
    }

    @Override
    public CustomNodeVo getTargetUserTaskCustomNode(String sequenceId, String taskId) {
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        Process process = bpmnModel.getProcesses().get(0);

        FlowElement flowElementSeq = process.getFlowElement(sequenceId);
        if (!(flowElementSeq instanceof SequenceFlow)) {
            log.error("FlowElement switch to SequenceFlow failed");
            return new CustomNodeVo();
        }
        SequenceFlow sequenceFlow = (SequenceFlow) flowElementSeq;
        FlowElement flowElementUserTask = process.getFlowElement(sequenceFlow.getTargetRef());
        if (!(flowElementUserTask instanceof UserTask)) {
            log.debug("FlowElement switch to UserTask failed");
            return new CustomNodeVo();
        }
        UserTask userTask = (UserTask) flowElementUserTask;
        String jsonStr = userTask.getDocumentation();
        CustomNodeVo customNodeVo = JSONObject.parseObject(jsonStr, CustomNodeVo.class);
        customNodeVo.setNodeId(userTask.getId());
        return customNodeVo;
    }

    @Override
    public CustomNodeVo getTargetUserTaskCustomNode(String procDefKey) {
        String procDefId = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(procDefKey).latestVersion().singleResult().getId();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(procDefId);
        Process process = bpmnModel.getProcesses().get(0);
        for (FlowElement flowElement : process.getFlowElements()) {
            if (flowElement instanceof StartEvent) {
                List<SequenceFlow> seqs = ((StartEvent) flowElement).getOutgoingFlows();
                //开始事件仅允许一条流出分支
                if (seqs.size() != 1) {
                    return null;
                }
                //流出分支的目标节点
                FlowElement node = process.getFlowElement(seqs.get(0).getTargetRef());
                if (node instanceof UserTask) {
                    String jsonStr = node.getDocumentation();
                    CustomNodeVo customNodeVo = JSONObject.parseObject(jsonStr, CustomNodeVo.class);
                    customNodeVo.setNodeId(node.getId());
                    return customNodeVo;
                } else {
                    return null;
                }
            }
        }

        return null;
    }

    @Override
    public List<WaitingToDo> getInTransitProcess(String userId, String queryText, int pageNum, int pageSize) {
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        if (StringUtils.isNotBlank(queryText)) {
            processInstanceQuery.or()
                    .processInstanceId(queryText)
                    .processDefinitionName(queryText)
                    .endOr();
        }
        List<WaitingToDo> waitingToDos = processInstanceQuery
                .startedBy(userId)
                .orderByStartTime()
                .desc()
                .list()
                .stream()
                .map(processInstance -> {
                    List<Task> tasks = taskService
                            .createTaskQuery()
                            .processInstanceId(processInstance.getId())
                            .list();
                    Task task = tasks.get(0);
                    WaitingToDo waitingToDo = new WaitingToDo();
                    waitingToDo.setTaskId(task.getId());
                    waitingToDo.setProcInstId(task.getProcessInstanceId());
                    waitingToDo.setProcDefName(processInstance.getProcessDefinitionName());
                    waitingToDo.setApplicant(processInstance.getStartUserId());
                    waitingToDo.setComment(getCommentByTaskId(task.getId()));
                    waitingToDo.setArrivalDate(task.getCreateTime());
                    CustomNodeVo customNodeVo = JSON.parseObject(task.getDescription(), CustomNodeVo.class);
                    customNodeVo.setNodeName(task.getName());
                    waitingToDo.setCurrNode(customNodeVo);
                    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                            .processDefinitionId(processInstance.getProcessDefinitionId())
                            .singleResult();
                    CustomProcess customProcess = JSON.parseObject(processDefinition.getDescription(), CustomProcess.class);
                    waitingToDo.setCustomProcess(customProcess);
                    return waitingToDo;
                }).collect(Collectors.toList());
        return waitingToDos;
    }

    @Override
    public Long getInTransitProcessCount(String userId, String queryText) {
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        if (StringUtils.isNotBlank(queryText)) {
            processInstanceQuery.or()
                    .processInstanceId(queryText)
                    .processDefinitionName(queryText)
                    .endOr();
        }
        return processInstanceQuery.startedBy(userId).count();
    }

    @Override
    public List<FinishedProcess> getLatelyClosedProcess(String userId, String queryText, int pageNum, int pageSize) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        if (StringUtils.isNotBlank(queryText)) {
            historicProcessInstanceQuery.or()
                    .processInstanceId(queryText)
                    .processDefinitionName(queryText)
                    .endOr();
        }
        List<HistoricProcessInstance> hisProList = historicProcessInstanceQuery
                .startedBy(userId)
                .finished()
                .orderByProcessInstanceEndTime()
                .desc()
                .listPage((pageNum - 1) * pageSize, pageSize);
        List<FinishedProcess> lists = new ArrayList<>();
        hisProList.forEach(historicProcessInstance -> {
            FinishedProcess finishedProcess = new FinishedProcess();
            finishedProcess.setCreateApplicant(historicProcessInstance.getStartUserId());
            finishedProcess.setProcInstId(historicProcessInstance.getId());
            finishedProcess.setProcDefName(historicProcessInstance.getProcessDefinitionName());
            if (StringUtils.isNotBlank(historicProcessInstance.getDeleteReason())) {
                finishedProcess.setClosedReason(historicProcessInstance.getDeleteReason());
            } else {
                finishedProcess.setClosedReason("流程结束");
            }
            finishedProcess.setStartDate(historicProcessInstance.getStartTime());
            finishedProcess.setEndDate(historicProcessInstance.getEndTime());
            finishedProcess.setHandTime(DateUtil.getHumanDisplayForTimediff(historicProcessInstance.getDurationInMillis()));
            lists.add(finishedProcess);
        });

        return lists;
    }

    @Override
    public Long getLatelyClosedProcessCount(String userId, String queryText) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        if (StringUtils.isNotBlank(queryText)) {
            historicProcessInstanceQuery.or()
                    .processInstanceId(queryText)
                    .processDefinitionName(queryText)
                    .endOr();
        }
        return historicProcessInstanceQuery.startedBy(userId).finished().count();
    }

    @Override
    public List<ProcessManager> getAllProcessInstance(String procDefName,
                                                      String applicant,
                                                      String status,
                                                      int pageNum, int pageSize) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        if (StringUtils.isNotBlank(status)) {
            switch (status) {
                case PROC_STATUS_FINISHED:
                    historicProcessInstanceQuery = historicProcessInstanceQuery.finished();
                    break;
                case PROC_STATUS_UNFINISHED:
                    historicProcessInstanceQuery = historicProcessInstanceQuery.unfinished();
                    break;
                case PROC_STATUS_DELETED:
                    historicProcessInstanceQuery = historicProcessInstanceQuery.deleted();
                    break;
                case PROC_STATUS_NOT_DELETED:
                    historicProcessInstanceQuery = historicProcessInstanceQuery.notDeleted();
                    break;
            }
        }
        if (StringUtils.isNotBlank(applicant)) {
            historicProcessInstanceQuery = historicProcessInstanceQuery.startedBy(applicant);
        }
        if (StringUtils.isNotBlank(procDefName)) {
            historicProcessInstanceQuery = historicProcessInstanceQuery.processInstanceNameLike(procDefName);
        }
        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery
                .orderByProcessInstanceStartTime().desc().listPage((pageNum - 1) * pageSize, pageSize);
        List<ProcessManager> processManagers = new ArrayList<>();
        historicProcessInstances.forEach(item -> {
            ProcessManager processManager = new ProcessManager();
            processManager.setProcInstId(item.getId());
            processManager.setProcName(item.getProcessDefinitionName());
            processManager.setApplicant(item.getStartUserId());
            processManager.setStartTime(DateUtil.getDateFormat(item.getStartTime(), DateUtil.FULL_TIME_SPLIT_PATTERN));
            if (item.getEndTime() == null) {
                //运行中的任务
                processManager.setProcStatus(PROC_STATUS_UNFINISHED);
                ActivityInstance ai = runtimeService.createActivityInstanceQuery().processInstanceId(item.getId()).unfinished().singleResult();
                processManager.setCurrNodeName(ai.getActivityName());
                if ("userTask".equals(ai.getActivityType())) {
                    List<IdentityLink> identityLinks = taskService.getIdentityLinksForTask(ai.getTaskId());
                    String candidates = identityLinks.stream().map(i -> {
                        if (StringUtils.isNotBlank(i.getGroupId())) {
                            return i.getGroupId();
                        } else if (StringUtils.isNotBlank(i.getUserId())) {
                            return i.getUserId();
                        } else {
                            return null;
                        }
                    }).collect(Collectors.joining(","));
                    processManager.setCurrCandidates(candidates);
                }
            } else if (StringUtils.isNotBlank(item.getDeleteReason())) {
                processManager.setProcStatus(PROC_STATUS_DELETED);
                processManager.setEndTime(DateUtil.getDateFormat(item.getEndTime(), DateUtil.FULL_TIME_SPLIT_PATTERN));
            } else {
                processManager.setProcStatus(PROC_STATUS_FINISHED);
                processManager.setEndTime(DateUtil.getDateFormat(item.getEndTime(), DateUtil.FULL_TIME_SPLIT_PATTERN));
            }

            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                    .processDefinitionId(item.getProcessDefinitionId())
                    .singleResult();
            if (processDefinition != null && StringUtils.isNotBlank(processDefinition.getDescription())){
                CustomProcess customProcess = JSON.parseObject(processDefinition.getDescription(), CustomProcess.class);
                processManager.setQryFormId(customProcess.getQryFormId());
                processManager.setQryFormTitle(customProcess.getQryFormTitle());
            }
            processManagers.add(processManager);
        });

        return processManagers;
    }

    @Override
    public Long getAllProcessInstanceCount(String procDefName, String applicant, String status) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        if (StringUtils.isNotBlank(status)) {
            switch (status) {
                case PROC_STATUS_FINISHED:
                    historicProcessInstanceQuery = historicProcessInstanceQuery.finished();
                    break;
                case PROC_STATUS_UNFINISHED:
                    historicProcessInstanceQuery = historicProcessInstanceQuery.unfinished();
                    break;
                case PROC_STATUS_DELETED:
                    historicProcessInstanceQuery = historicProcessInstanceQuery.deleted();
                    break;
                case PROC_STATUS_NOT_DELETED:
                    historicProcessInstanceQuery = historicProcessInstanceQuery.notDeleted();
                    break;
            }
        }
        if (StringUtils.isNotBlank(applicant)) {
            historicProcessInstanceQuery = historicProcessInstanceQuery.startedBy(applicant);
        }
        if (StringUtils.isNotBlank(procDefName)) {
            historicProcessInstanceQuery = historicProcessInstanceQuery.processInstanceNameLike(procDefName);
        }

        return historicProcessInstanceQuery.count();
    }

    @Override
    public void deleteDeployment(String procDefKey, String version, boolean deleteProcessInstants) {
        ProcessDefinitionQuery pdq = repositoryService.createProcessDefinitionQuery();
        pdq.processDefinitionKey(procDefKey);
        if (version != null) {
            pdq.processDefinitionVersion(Integer.valueOf(version));
        }
        List<ProcessDefinition> pds = pdq.list();
        pds.forEach(pd -> repositoryService.deleteDeployment(pd.getDeploymentId(), deleteProcessInstants));
    }

    @Override
    public void suspendDeployment(String procDefKey, boolean suspendProcessInstances, LocalDate suspensionDate) {
        Date date = suspensionDate == null ? null : DateUtil.localDateToDate(suspensionDate);
        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey).list();
        processDefinitions.forEach( pd -> repositoryService.suspendProcessDefinitionById(pd.getId(), suspendProcessInstances, date));
    }

    @Override
    public void activeDeployment(String procDefKey, boolean activateProcessInstances, LocalDate activationDate) {
        Date date = activationDate == null ? null : DateUtil.localDateToDate(activationDate);
        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey).list();
        processDefinitions.forEach( pd -> repositoryService.activateProcessDefinitionById(pd.getId(), activateProcessInstances, date));
    }

    @Override
    public Boolean getSuspendStatusByProcDefKey(String procDefKey) {
        ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey).latestVersion().singleResult();
        if (pd == null) {
            return null;
        } else {
            return pd.isSuspended();
        }
    }

    @Override
    public List<HistoricProcessInstance> getFinishedProcInst(Date date) {
        return historyService.createHistoricProcessInstanceQuery()
                .finishedBefore(date)
                .list();
    }

    @Override
    public void deleteHistoryProcInst(String procInstId) {
        historyService.deleteHistoricProcessInstance(procInstId);
    }
}
